use crate::{constants::{DefaultFingerprintType, INDEX_FP_STORE_INFO_LEN}, recipe::ChunkPointer, container::ContainerId};
use sled::IVec;
use util::RawData;

use super::error::{IndexResult, IndexError};

pub type ChunkInfo = ContainerId;
pub const CHUNK_INFO_SIZE:usize = std::mem::size_of::<ChunkInfo>();
pub const IVEC_LAYOUT_SIZE:usize = std::mem::size_of::<FpValueIVecLayout>();

#[derive(Debug,RawData)]
pub struct FpValueIVecLayout {
    len:u8,
    info:[ChunkInfo;INDEX_FP_STORE_INFO_LEN],
}

impl FpValueIVecLayout {
    pub fn new() -> Self {
        Self { 
            len:0,
            info:[0;INDEX_FP_STORE_INFO_LEN],
        }
    }

    pub fn with_first(first_info:ChunkInfo) -> Self {
        Self { 
            len:1,
            info:{
                let mut info:[ChunkInfo;INDEX_FP_STORE_INFO_LEN] = [0;INDEX_FP_STORE_INFO_LEN];
                info[0] = first_info;
                info
            }
        }
    }   

    pub fn push_chunk_info(&mut self,info:ChunkInfo) {
        let len = self.len as usize;
        self.info[len] = info;
        self.len = ((len + 1) % INDEX_FP_STORE_INFO_LEN) as u8;
    }

    pub fn last_chunk_info(&self) -> ChunkInfo {
        if self.len == 0 {
            self.info[INDEX_FP_STORE_INFO_LEN - 1]
        } else {
            self.info[self.len as usize - 1]
        }
    }
    
    pub fn from_ivec(v:&IVec) -> &FpValueIVecLayout {
        unsafe { 
            assert_eq!(v.len(),IVEC_LAYOUT_SIZE);
            &*((v as &[u8]) as *const [u8] as *const u8 as usize as *const FpValueIVecLayout)
        }
    }

    pub fn from_ivec_mut(v:&mut IVec) -> &mut FpValueIVecLayout {
        unsafe { 
            assert_eq!(v.len(),IVEC_LAYOUT_SIZE);
            &mut *((v as &mut [u8]) as *mut [u8] as *mut u8 as usize as *mut FpValueIVecLayout)
        }
    }
}


pub trait FpKvStore {
    fn insert_with_fp(&self,fp:&DefaultFingerprintType,info:&ChunkInfo) -> IndexResult<()>;
    fn find_with_fp<T:ChunkPointer>(&self,fp:&DefaultFingerprintType) -> IndexResult<Option<Box<T>>>;
}

impl FpKvStore for sled::Db {
    fn insert_with_fp(&self,fp:&DefaultFingerprintType,info:&ChunkInfo) -> IndexResult<()> {
        self.fetch_and_update(fp, |old|{
            match old {
                Some(old) => {
                    let mut ivec = IVec::from(old);
                    let ivec_layout = FpValueIVecLayout::from_ivec_mut(&mut ivec);
                    ivec_layout.push_chunk_info(*info);
                    Some(ivec)
                }
                None => {
                    let ivec_layout = FpValueIVecLayout::with_first(*info);
                    let ivec = IVec::from(ivec_layout.as_raw_data());
                    Some(ivec)
                }
            }
        })
        .map(|_|())
        .map_err(|e|IndexError::FpInsert(e))
    }

    fn find_with_fp<T:ChunkPointer>(&self,fp:&DefaultFingerprintType) -> IndexResult<Option<Box<T>>> {
        self.get(fp)
            .map(|value|value.map(|ivec|{
                assert_eq!(ivec.len(),IVEC_LAYOUT_SIZE);
                let ivec_layout = FpValueIVecLayout::from_ivec(&ivec);
                let id:ChunkInfo = ivec_layout.last_chunk_info();
                Box::new(T::new(id,0,fp))
            }))
            .map_err(|e|IndexError::FpFind(e))
    }
}